package com.example.videoplayer.service.impl;

import com.example.videoplayer.service.HLSConversionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class HLSConversionServiceImpl implements HLSConversionService {
    private static final Logger logger = LoggerFactory.getLogger(HLSConversionServiceImpl.class);
    private static final String HLS_DIR = "src/main/resources/hls/";
    private static final ConcurrentHashMap<String, Double> conversionProgress = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, Process> activeConversions = new ConcurrentHashMap<>();
    private static final Pattern DURATION_PATTERN = Pattern.compile("Duration: (\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{2})");
    private static final Pattern TIME_PATTERN = Pattern.compile("time=(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{2})");

    @PostConstruct
    public void init() {
        try {
            Path hlsPath = Paths.get(HLS_DIR).toAbsolutePath();
            Files.createDirectories(hlsPath);
            logger.info("HLS directory created at: {}", hlsPath);
        } catch (Exception e) {
            logger.error("Failed to create HLS directory", e);
        }
    }

    @Override
    public Mono<Void> convertToHLS(String inputPath) {
        return Mono.<Void>create(sink -> {
            try {
                File inputFile = new File(inputPath);
                if (!inputFile.exists()) {
                    sink.error(new IllegalArgumentException("Input file does not exist: " + inputPath));
                    return;
                }

                String fileName = inputFile.getName();
                String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
                Path outputDir = Paths.get(HLS_DIR, baseName).toAbsolutePath();
                
                logger.info("Starting conversion for file: {}", fileName);
                logger.debug("Input path: {}, Output dir: {}", inputPath, outputDir);
                
                // 创建输出目录
                Files.createDirectories(outputDir);
                
                // 检查 FFmpeg 是否可用
                checkFFmpegAvailability();
                
                // 设置FFmpeg命令
                List<String> command = new ArrayList<>();
                command.add("ffmpeg");
                command.add("-y"); // 覆盖已存在的文件
                command.add("-i");
                command.add(inputPath);
                command.add("-c:v");
                command.add("libx264");
                command.add("-preset");
                command.add("fast");
                command.add("-crf");
                command.add("23");
                command.add("-c:a");
                command.add("aac");
                command.add("-b:a");
                command.add("128k");
                command.add("-f");
                command.add("hls");
                command.add("-hls_time");
                command.add("10");
                command.add("-hls_list_size");
                command.add("0");
                command.add("-hls_segment_filename");
                command.add(outputDir.resolve("%03d.ts").toString());
                command.add(outputDir.resolve("playlist.m3u8").toString());

                ProcessBuilder pb = new ProcessBuilder(command);
                pb.redirectErrorStream(true);
                pb.directory(outputDir.toFile());
                
                logger.debug("Executing FFmpeg command: {}", String.join(" ", command));
                
                Process process = pb.start();
                activeConversions.put(fileName, process);
                conversionProgress.put(fileName, 0.0);
                
                Thread monitorThread = new Thread(() -> monitorConversion(process, fileName));
                monitorThread.setDaemon(true);
                monitorThread.start();
                
                int exitCode = process.waitFor();
                
                activeConversions.remove(fileName);
                conversionProgress.remove(fileName);
                
                if (exitCode != 0) {
                    String errorMessage = "FFmpeg conversion failed with exit code: " + exitCode;
                    logger.error("{} for file: {}", errorMessage, fileName);
                    sink.error(new RuntimeException(errorMessage));
                    return;
                }
                
                logger.info("Conversion completed successfully for file: {}", fileName);
                sink.success();
            } catch (Exception e) {
                logger.error("Error during HLS conversion", e);
                sink.error(e);
            }
        }).subscribeOn(Schedulers.boundedElastic());
    }

    private void checkFFmpegAvailability() {
        try {
            Process process = new ProcessBuilder("ffmpeg", "-version")
                    .redirectErrorStream(true)
                    .start();
            int exitCode = process.waitFor();
            if (exitCode != 0) {
                throw new RuntimeException("FFmpeg is not available");
            }
        } catch (Exception e) {
            throw new RuntimeException("FFmpeg is not installed or not accessible", e);
        }
    }

    @Override
    public String getHLSPath(String fileName) {
        try {
            String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
            return "/hls/" + baseName + "/playlist.m3u8";
        } catch (Exception e) {
            logger.error("Error generating HLS path for filename: {}", fileName, e);
            throw new IllegalArgumentException("Invalid filename: " + fileName);
        }
    }

    @Override
    public Mono<Double> getConversionProgress(String fileName) {
        return Mono.just(conversionProgress.getOrDefault(fileName, 0.0));
    }

    @Override
    public boolean isConversionInProgress(String fileName) {
        return activeConversions.containsKey(fileName);
    }

    private void monitorConversion(Process process, String fileName) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            double totalDuration = -1;
            
            while ((line = reader.readLine()) != null) {
                logger.trace("FFmpeg output: {}", line);
                
                // 解析总时长
                if (totalDuration == -1) {
                    Matcher durationMatcher = DURATION_PATTERN.matcher(line);
                    if (durationMatcher.find()) {
                        totalDuration = parseTime(durationMatcher);
                        logger.debug("Total duration for {}: {} seconds", fileName, totalDuration);
                    }
                }
                
                // 解析当前进度
                Matcher timeMatcher = TIME_PATTERN.matcher(line);
                if (timeMatcher.find() && totalDuration > 0) {
                    double currentTime = parseTime(timeMatcher);
                    double progress = Math.min((currentTime / totalDuration) * 100, 100.0);
                    conversionProgress.put(fileName, progress);
                    logger.debug("Conversion progress for {}: {:.2f}%", fileName, progress);
                }
            }
        } catch (Exception e) {
            logger.error("Error monitoring conversion for {}: {}", fileName, e.getMessage());
        }
    }

    private double parseTime(Matcher matcher) {
        int hours = Integer.parseInt(matcher.group(1));
        int minutes = Integer.parseInt(matcher.group(2));
        int seconds = Integer.parseInt(matcher.group(3));
        int centiseconds = Integer.parseInt(matcher.group(4));
        return hours * 3600 + minutes * 60 + seconds + centiseconds / 100.0;
    }
}
 